Eg.BackgroundMap = Eg.Map.extend({
  tiles: null,
  scroll: { x: 0, y: 0 },
  distance: 1,
  repeat: false,
  tilesetName: '',
  foreground: false,
  enabled: true,

  preRender: false,
  preRenderedChunks: null,
  chunkSize: 512,
  debugChunks: false,


  anims: {},


  init: function (tilesize, data, tileset) {
    this.parent(tilesize, data);
    this.setTileset(tileset);
  },


  setTileset: function (tileset) {
    this.tilesetName = tileset instanceof Eg.Image ? tileset.path : tileset;
    this.tiles = new Eg.Image(this.tilesetName);
    this.tiles.load();
    this.preRenderedChunks = null;
  },


  setScreenPos: function (x, y) {
    this.scroll.x = x / this.distance;
    this.scroll.y = y / this.distance;
  },


  preRenderMapToChunks: function () {
    var totalWidth = this.width * this.tilesize * Eg.sys.scale,
			totalHeight = this.height * this.tilesize * Eg.sys.scale;

    var chunkCols = Math.ceil(totalWidth / this.chunkSize),
			chunkRows = Math.ceil(totalHeight / this.chunkSize);

    this.preRenderedChunks = [];
    for (var y = 0; y < chunkRows; y++) {
      this.preRenderedChunks[y] = [];

      for (var x = 0; x < chunkCols; x++) {


        var chunkWidth = (x == chunkCols - 1)
					? totalWidth - x * this.chunkSize
					: this.chunkSize;

        var chunkHeight = (y == chunkRows - 1)
					? totalHeight - y * this.chunkSize
					: this.chunkSize;

        this.preRenderedChunks[y][x] = this.preRenderChunk(x, y, chunkWidth, chunkHeight);
      }
    }
  },


  preRenderChunk: function (cx, cy, w, h) {
    var tw = w / this.tilesize / Eg.sys.scale + 1;
    th = h / this.tilesize / Eg.sys.scale + 1;

    var nx = (cx * this.chunkSize / Eg.sys.scale) % this.tilesize,
			ny = (cy * this.chunkSize / Eg.sys.scale) % this.tilesize;

    var tx = Math.floor(cx * this.chunkSize / this.tilesize / Eg.sys.scale),
			ty = Math.floor(cy * this.chunkSize / this.tilesize / Eg.sys.scale);


    var chunk = Eg.$new('canvas');
    chunk.width = w;
    chunk.height = h;

    var oldContext = Eg.sys.context;
    Eg.sys.context = chunk.getContext("2d");

    for (var x = 0; x < tw; x++) {
      for (var y = 0; y < th; y++) {
        if (x + tx < this.width && y + ty < this.height) {
          var tile = this.data[y + ty][x + tx];
          if (tile) {
            this.tiles.drawTile(
							x * this.tilesize - nx, y * this.tilesize - ny,
							tile - 1, this.tilesize
						);
          }
        }
      }
    }
    Eg.sys.context = oldContext;

    return chunk;
  },


  draw: function () {
    if (!this.tiles.loaded || !this.enabled) {
      return;
    }

    if (this.preRender) {
      this.drawPreRendered();
    }
    else {
      this.drawTiled();
    }
  },


  drawPreRendered: function () {
    if (!this.preRenderedChunks) {
      this.preRenderMapToChunks();
    }

    var dx = Eg.sys.getDrawPos(this.scroll.x),
			dy = Eg.sys.getDrawPos(this.scroll.y);


    if (this.repeat) {
      dx %= this.width * this.tilesize * Eg.sys.scale;
      dy %= this.height * this.tilesize * Eg.sys.scale;
    }

    var minChunkX = Math.max(Math.floor(dx / this.chunkSize), 0),
			minChunkY = Math.max(Math.floor(dy / this.chunkSize), 0),
			maxChunkX = Math.ceil((dx + Eg.sys.realWidth) / this.chunkSize),
			maxChunkY = Math.ceil((dy + Eg.sys.realHeight) / this.chunkSize),
			maxRealChunkX = this.preRenderedChunks[0].length,
			maxRealChunkY = this.preRenderedChunks.length;


    if (!this.repeat) {
      maxChunkX = Math.min(maxChunkX, maxRealChunkX);
      maxChunkY = Math.min(maxChunkY, maxRealChunkY);
    }


    var nudgeY = 0;
    for (var cy = minChunkY; cy < maxChunkY; cy++) {

      var nudgeX = 0;
      for (var cx = minChunkX; cx < maxChunkX; cx++) {
        var chunk = this.preRenderedChunks[cy % maxRealChunkY][cx % maxRealChunkX];

        var x = -dx + cx * this.chunkSize - nudgeX;
        var y = -dy + cy * this.chunkSize - nudgeY;
        Eg.sys.context.drawImage(chunk, x, y);
        Eg.Image.drawCount++;

        if (this.debugChunks) {
          Eg.sys.context.strokeStyle = '#f0f';
          Eg.sys.context.strokeRect(x, y, this.chunkSize, this.chunkSize);
        }

        // If we repeat in X and this chunks width wasn't the full chunk size
        // and the screen is not already filled, we need to draw anohter chunk
        // AND nudge it to be flush with the last chunk
        if (this.repeat && chunk.width < this.chunkSize && x + chunk.width < Eg.sys.realWidth) {
          nudgeX = this.chunkSize - chunk.width;
          maxChunkX++;
        }
      }

      // Same as above, but for Y
      if (this.repeat && chunk.height < this.chunkSize && y + chunk.height < Eg.sys.realHeight) {
        nudgeY = this.chunkSize - chunk.height;
        maxChunkY++;
      }
    }
  },


  drawTiled: function () {
    var tile = 0,
			anim = null,
			tileOffsetX = (this.scroll.x / this.tilesize).toInt(),
			tileOffsetY = (this.scroll.y / this.tilesize).toInt(),
			pxOffsetX = this.scroll.x % this.tilesize,
			pxOffsetY = this.scroll.y % this.tilesize,
			pxMinX = -pxOffsetX - this.tilesize,
			pxMinY = -pxOffsetY - this.tilesize,
			pxMaxX = Eg.sys.width + this.tilesize - pxOffsetX,
			pxMaxY = Eg.sys.height + this.tilesize - pxOffsetY;


    // FIXME: could be sped up for non-repeated maps: restrict the for loops
    // to the map size instead of to the screen size and skip the 'repeat'
    // checks inside the loop.

    for (var mapY = -1, pxY = pxMinY; pxY < pxMaxY; mapY++, pxY += this.tilesize) {
      var tileY = mapY + tileOffsetY;

      // Repeat Y?
      if (tileY >= this.height || tileY < 0) {
        if (!this.repeat) { continue; }
        tileY = tileY > 0
					? tileY % this.height
					: ((tileY + 1) % this.height) + this.height - 1;
      }

      for (var mapX = -1, pxX = pxMinX; pxX < pxMaxX; mapX++, pxX += this.tilesize) {
        var tileX = mapX + tileOffsetX;

        // Repeat X?
        if (tileX >= this.width || tileX < 0) {
          if (!this.repeat) { continue; }
          tileX = tileX > 0
						? tileX % this.width
						: ((tileX + 1) % this.width) + this.width - 1;
        }

        // Draw!
        if ((tile = this.data[tileY][tileX])) {
          if ((anim = this.anims[tile - 1])) {
            anim.draw(pxX, pxY);
          }
          else {
            this.tiles.drawTile(pxX, pxY, tile - 1, this.tilesize);
          }
        }
      } // end for x
    } // end for y
  }
});